package com.demo.ping.component

import com.demo.ping.orm.entity.ErrorLogs
import com.demo.ping.orm.enumeration.ResponseEnum
import com.demo.ping.service.ErrorLogsService
import com.demo.ping.service.KafkaService
import com.demo.ping.service.PingService
import com.hazelcast.core.HazelcastInstance
import com.hazelcast.map.IMap
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import org.springframework.web.reactive.function.client.WebClientResponseException
import reactor.core.publisher.Mono
import spock.lang.Specification

class PingSchedulerTest extends Specification {

    def pingService = Mock(PingService)
    def errorLogsService = Mock(ErrorLogsService)
    def kafkaService = Mock(KafkaService)
    def hazelcastInstance = Mock(HazelcastInstance)
    def requestTimestamps = Mock(IMap)

    def pingScheduler

    private static final Logger log = LoggerFactory.getLogger(PingSchedulerTest.class)

    def setup() {
        hazelcastInstance.getMap(_ as String) >> requestTimestamps
        pingScheduler = new PingScheduler(errorLogsService, kafkaService, hazelcastInstance, pingService)
        pingScheduler.init()
    }


    def "test schedulePing when requests are within limit"() {
        given:
        def currentTime = System.currentTimeMillis()
        requestTimestamps.size() >> 1
        requestTimestamps.entrySet() >> [
            ["requestKey_1", currentTime - 500]
        ].collectEntries { it }.entrySet().toSet()
        pingService.sayHello(_ as String) >> Mono.just("world")

        when:
        pingScheduler.schedulePing()

        then:
        1 * kafkaService.sendMessage("logInfo", ResponseEnum.REQ_SENT_PONG_RESPOND.getReason())
        0 * log.info(ResponseEnum.REQ_SENT_PONG_RESPOND.getReason())
    }

    def "test schedulePing when requests exceed limit"() {
        given:
        def currentTime = System.currentTimeMillis()
        requestTimestamps.size() >> 3
        requestTimestamps.entrySet() >> [
            ["requestKey_1", currentTime - 100],
            ["requestKey_2", currentTime - 200],
            ["requestKey_3", currentTime - 300]
        ].collectEntries { it }.entrySet().toSet()

        when:
        pingScheduler.schedulePing()

        then:
        0 * pingService.sayHello(_)
        0 * log.error(ResponseEnum.NOT_SENT_BY_RATE_LIMITED.getReason())
        1 * errorLogsService.saveErrorLog(_ as ErrorLogs)
    }

    def "test cleanUpOldRequests"() {
        given:
        def currentTime = System.currentTimeMillis()
        requestTimestamps.entrySet() >> [
            ["requestKey_1", currentTime - 1500],
            ["requestKey_2", currentTime - 500]
        ].collectEntries { it }.entrySet().toSet()
        requestTimestamps.remove(_ as String) >> null

        when:
        pingScheduler.cleanUpOldRequests()

        then:
        1 * requestTimestamps.remove("requestKey_1")
        0 * requestTimestamps.remove("requestKey_2")
    }

    def "test handlePingError with too many requests"() {
        given:
        def error = new WebClientResponseException(HttpStatus.TOO_MANY_REQUESTS.value(), "Too Many Requests", null, null, null)
        def traceId = UUID.randomUUID().toString()

        when:
        pingScheduler.handlePingError(error,traceId)

        then:
        0 * log.error(ResponseEnum.REQ_SENT_PONG_FAIL.getReason())
    }

    def "test handlePingError with other errors"() {
        given:
        def error = new RuntimeException("Connection refused")
        def traceId = UUID.randomUUID().toString()

        when:
        pingScheduler.handlePingError(error, traceId)

        then:
        0 * log.error("Connection refused, please check")
    }

    def "test logRateLimitedError"() {
        when:
        pingScheduler.logRateLimitedError()

        then:
        0 * log.error(ResponseEnum.NOT_SENT_BY_RATE_LIMITED.getReason())
        1 * errorLogsService.saveErrorLog(_ as ErrorLogs)
    }
}
